#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QDebug>

int Widget::_count = 0;
int number = 1;

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    QPushButton* button1 = new QPushButton(this);
    button1->setText("关闭窗口");
    button1->move(200, 200);

    //Qt中可以使用connect这样的函数，把一个信号和一个槽关联起来
    //后续只要信号触发了，Qt就会自动的执行槽函数
    //所谓的“槽函数”本质上也是一种“回调函数”(callback)
    //一定是先把信号的处理方式准备好，再触发信号
    //Qt中，一定是先关联号信号和槽，然后再触发这个信号.顺序不能颠倒，否则信号就不知道如何处理了(错过了)

    //这里是调用qt自带槽函数
    connect(button1, &QPushButton::clicked, this, &Widget::close);

    QPushButton* button2 = new QPushButton(this);
    button2->setText("改变窗口的标题同时打印信息");
    button2->move(300, 300);

    //这里是调用自定义的槽函数
    //clicked是他的父类定义的信号(QAbstractButton),定义的信号.
    connect(button2, &QPushButton::clicked, this, &Widget::handleClicked);

    //click是他的父类定义的信号(QAbstractButton),定义的槽函数,作用就是在调用的时候相当于点击了一下按钮
    //槽函数可以像普通的成员函数那样直接调用。在Qt中，槽函数本质上就是类中的普通成员函数，只不过它们也能被信号所触发
    //这意味着你可以在代码中直接调用一个槽函数，无论是否有信号与之连接。
    //button2->click();

    //这里是调用自定义的信号以及自定义的槽函数
    connect(this, &Widget::mySignal1, this, &Widget::myHandleSignal1);
    //建立连接，不代表信号发出来了
    //如何才能触发出自定义的信号呢？
    //Qt内置的信号，都不需要咱们手动通过代码来触发，用户在GUI，进行某些操作，就会自动触发对应信号.（发射信号的代码已经内置到Qt框架中了）
    //通过emit 信号函数,这种形式来进行发送自定义信号
    //在qt5中,emit进行了优化,即使不写emit，信号也能发出去
    //真正的操作都包含在mySignal1内部生成的函数定义了
    //即使如此，实际开发中，还是建议，把emit都加上吧
    //加上代码可读性更高，更明显的标识出，这里是发射自定义的信号了

    //发送自定义信号可以是任意时候,不一定非得在构造函数里
    emit mySignal1();

    //下面试试信号和槽函数是带有参数的例子
    connect(button2, &QPushButton::clicked, this, &Widget::handleClicked);
    //connect(this, &Widget::mySignal2, this, &Widget::handleMySignal2);

    //信号和槽都可以进行重载,记住重载之后,就需要使用static_cast进行对应的匹配
    connect(this, static_cast<void (Widget::*)(const QString&, int, const QString&)>(&Widget::mySignal2),
            this, static_cast<void (Widget::*)(const QString&, int)>(&Widget::handleMySignal2));

    connect(this, static_cast<void (Widget::*)(const QString&, const QString&)>(&Widget::mySignal2),
            this, static_cast<void (Widget::*)(const QString&, const QString&)>(&Widget::handleMySignal2));

    connect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleTest1);

    //定义槽函数的时候，也是可以使用lambda表达式的
    //我们下面使用lambda表达式来写槽函数

    //另外也要确认捕获到lambda内部的变量是有意义的~~回调函数执行时机是不确定的(用户啥时候点击按钮不知道的)
    //无论何时用户点击了按钮，捕获到的变量都能正确使用
    //所以我们需要注意捕捉变量的生命周期,特别是引用的时候
    //由于此处button是new出来的变量，生命周期跟随整个窗口(挂到对象树上，窗口关闭才会释放)
    //这个东西就可以在后面随时使用了
    //类似this 指向的对象widget
    QPushButton* button_lambda = new QPushButton(this);
    button_lambda->setText("lambda");
    button_lambda->move(250, 250);

    //lambda语法是C++11中引l入的.对于Qt5及其更高版本，默认就是按照C++11来编译的
    //如果使用Qt4或者更老的版本，就需要手动在.pro文件中加上C++11的编译选项
    //CONFIG += C++11
    connect(button_lambda, &QPushButton::clicked, this, [=](){
        qDebug() << "lambda被执行了";
        button_lambda->move(50, 50);
        this->move(100, 100);
    });
}

//这里详细探讨一下connect函数
//我们看看文档里面connect函数的原型是什么
//QMetaObject::Connection QObject::connect(const QObject *sender,
//const char *signal,
//const QObject *receiver,
//const char *method,
//Qt::ConnectionType type = Qt::AutoConnection)

//函数要求第二个和第四个的参数是const char*,但是我们具体调用的时候可以直接传入函数指针,是怎么回事?
//文档里面的原型是以前旧版本的Qt的connect函数的声明
//给信号参数传参，要搭配一个SIGNAL宏
//给槽参数传参，搭配一个SLOT宏
//connect(button,SIGNAL(&QPushButton:clicked),this,SLOT(&Widget:close));
//这些宏将传入的函数指针转成char*,就可以进行正常调用了

//但是我们直接进行函数的跳转,我们可以看到qt新版本重载的connect函数
//template <typename Func1, typename Func2>
//static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
//                                 const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot,
//                                 Qt::ConnectionType type = Qt::AutoConnection)

//载版本中，第二个参数和第四个参数成了泛型参数．允许咱们传入任意类型的函数指针了
//此时connect函数就带有了一定的参数检查功能.如果你传入的第一个参数和第二个参数不匹配
//或者第三个参数和第四个参数不匹配（2，4参数的函数指针，不是1，3参数的成员函数)此时代码编译出错


Widget::~Widget()
{
    delete ui;
}

void Widget::myHandleSignal1()
{
    this->setWindowTitle("直接改变窗口标题!");
}

void Widget::handleClicked()
{
    // 按下按钮, 修改一下窗口标题
    this->setWindowTitle("按钮已经按下1!");

    if (Widget::_count % 2 == 0){
        emit mySignal2("文本打印测试", 1, "你好");
        emit mySignal2("文本打印测试", "你好");
    }
    ++Widget::_count;
}

void Widget::handleMySignal2(const QString& text, int number)
{
    qDebug() << text << number;
}

void Widget::handleMySignal2(const QString &text, const QString &text1)
{
    qDebug() << text << text1;
}

//通过ui里面右键对应的控件,选择信号,可以设置这个控件信号所对应的槽函数
//QtCreator直接给我们生成好了一个函数
//除了通过connect来连接信号槽之外，还可以通过函数名字的方式来自动连接
//一般是objectName + 信号的名字
//当函数名符合上述规则之后，Qt就能自动的把信号和槽给建立上联系
//是在自动生成的ui_widget.h中调用的QMetaobject::connectSlotsByName(Widget)
//Qt中调用这个函数的时候，就会触发上述自动连接信号槽的规则
//不需要通过connect来进行连接
//如果我们通过图形化界面创建控件，还是推荐使用这种快速的方式来连接信号槽
//如果我们是通过代码的方式来创建控件，还是得手动connect
void Widget::on_MyPushButton_clicked()
{
    // 按下按钮, 修改一下窗口标题
     this->setWindowTitle("按钮已经按下2!");
}

void Widget::on_pushButton_clicked()
{
    //1. 先断开 pushButton 原来的信号槽
    //如果没有 disconnect, 就会构成 一个信号绑定了两个槽函数. 触发信号的时候, 两个槽函数都会执行
    //disconnect用的比较少的大部分的情况下，把信号和槽连上了之后，就不必管了，主动断开往往是把信号重新绑定到另一个槽函数上
    disconnect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleTest1);

    // 2. 重新绑定信号槽
    if (number == 1){
        connect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleTest2);
        ++number;
    }
}

void Widget::handleTest2()
{
    qDebug() << "test2";
}

void Widget::handleTest1()
{
    qDebug() << "test1";
}
